package com.heima.wemedia.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.common.constants.message.ArticleForEsConstants;
import com.heima.common.constants.message.NewsAutoScanConstants;
import com.heima.common.constants.message.WmNewsMessageConstants;
import com.heima.common.constants.wemedia.WemediaConstants;
import com.heima.common.exception.CustException;
import com.heima.common.exception.CustomException;
import com.heima.model.Utils.threadlocal.WmThreadLocalUtils;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.dto.NewsAuthDto;
import com.heima.model.wemedia.dto.WmNewsDto;
import com.heima.model.wemedia.dto.WmNewsPageReqDto;
import com.heima.model.wemedia.pojo.WmMaterial;
import com.heima.model.wemedia.pojo.WmNews;
import com.heima.model.wemedia.pojo.WmNewsMaterial;
import com.heima.model.wemedia.pojo.WmUser;
import com.heima.model.wemedia.vo.WmNewsVo;
import com.heima.wemedia.mapper.MaterialMapper;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmNewsMaterialMapper;
import com.heima.wemedia.mapper.WmUserMapper;
import com.heima.wemedia.service.WmNewsService;
import com.sun.xml.bind.v2.TODO;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.asm.Advice;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by ZYJ on 2021/5/28 21:32
 */
@Service
@Slf4j
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {
    @Value("${file.oss.web-site}")
    private String webSite;
    @Autowired
    private WmNewsMaterialMapper wmNewsMaterialMapper;
    @Autowired
    private MaterialMapper materialMapper;
    @Autowired
    KafkaTemplate<String, String> kafkaTemplate;

    /**
     * 自媒体文章列表查询
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult findAll(WmNewsPageReqDto dto) {
        if (dto == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2.用户是否登录
        WmUser user = WmThreadLocalUtils.getUser();
        if (user == null) {
            CustException.cust(AppHttpCodeEnum.NEED_LOGIN);
        }
        dto.checkParam();
        //3. 封装查询条件
        //分页条件
        IPage<WmNews> pageReq = new Page(dto.getPage(), dto.getSize());

        LambdaQueryWrapper<WmNews> queryWrapper = Wrappers.<WmNews>lambdaQuery();
        //查看装填条件是否存在
        if (dto.getStatus() != null) {
            queryWrapper.eq(WmNews::getStatus, dto.getStatus());
        }
        //查看关键字是否存在
        if (StringUtils.isNotBlank(dto.getKeyword())) {
            queryWrapper.like(WmNews::getTitle, dto.getKeyword());
        }
        //查看频道ID是否存在
        if (dto.getChannelId() != null) {
            queryWrapper.eq(WmNews::getChannelId, dto.getChannelId());
        }
        //发布日期 范围 是否存在
        if (dto.getBeginPubdate() != null && dto.getEndPubdate() != null) {
            queryWrapper.between(WmNews::getPublishTime, dto.getBeginPubdate(), dto.getEndPubdate());
        }
        //登录用户的id条件
        queryWrapper.eq(WmNews::getUserId, user.getId());
        // 按照publish_time 发布时间降序
        queryWrapper.orderByDesc(WmNews::getPublishTime);
        //4.执行查询
        IPage<WmNews> pageResult = this.page(pageReq, queryWrapper);
        //5.封装返回结果
        PageResponseResult pageResponseResult = new PageResponseResult(dto.getPage(), dto.getSize(), pageResult.getTotal());
        pageResponseResult.setData(pageResult.getRecords());
        pageResponseResult.setHost(webSite);
        return pageResponseResult;
    }

    /**
     * 自媒体文章发布,修改,草稿
     *
     * @param dto
     * @return
     */
    @Transactional
    @Override
    public ResponseResult submitNews(WmNewsDto dto) {
        //1. 检查参数 封装WMNews对象
        // 1.1 检查文章内容
        if (StringUtils.isBlank(dto.getContent())) {
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID, "文章内容不能为空");
        }
        // 1.2 文章的状态 只能为0 草稿 1
        if (!dto.getStatus().equals(WemediaConstants.WM_NEWS_DRAFT_STATUS) && !dto.getStatus().equals(WemediaConstants.WM_NEWS_SUMMIT_STATUS)) {
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID, "状态类型异常");
        }
        // 1.3 是否登录
        WmUser user = WmThreadLocalUtils.getUser();
        if (user == null) {
            throw new CustomException(AppHttpCodeEnum.NEED_LOGIN);
        }
        // 1.4 基于dto封装wmNews
        WmNews wmNews = new WmNews();
        BeanUtils.copyProperties(dto, wmNews);
        // 1.5 如果type为自动生成, 暂时将type设置为null 数据库中用的是无符号字段,无法存储负数
        if (dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)) {
            wmNews.setType(null);
        }
        // 1.6 如果封装images集合不为null 拼接成字符串,并替换前缀路径
        List<String> imageList = dto.getImages(); //封面集合
        if (imageList != null && imageList.size() > 0) {
            String str = imageListToStr(imageList);
            wmNews.setImages(str);
        }
        //2. 保存或修改 WmNews对象
        wmNews.setUserId(user.getId());
        saveOrUpdateWmNews(wmNews);

        //3.保存文章 和素材的关联关系
        //3.1 抽取文章内容涉及的图片
        List<String> contentImages = parseContentImages(dto.getContent());
        //3.2 保存 文章内容所引用的图片素材的关联关系
        if (dto.getStatus().equals(WemediaConstants.WM_NEWS_SUMMIT_STATUS) && contentImages != null && contentImages.size() > 0) {
            saveRelativeInfoForContent(contentImages, wmNews);
        }
        //3.3 保存 文章封面所引用的图片素材的关联关系
        if (dto.getStatus().equals(WemediaConstants.WM_NEWS_SUMMIT_STATUS)) {
            // 如果type 类型为-1 需要从文章内容图片中生成文章封面
            saveRelativeInfoForCover(contentImages, wmNews, dto);
            kafkaTemplate.send(NewsAutoScanConstants.WM_NEWS_AUTO_SCAN_TOPIC, String.valueOf(wmNews.getId()));
            log.info("发表文章成功,并通过kafka通知admin端审核==={}", wmNews.getId());
        }


        return ResponseResult.okResult();

    }

    /**
     * 修改文章时属性回显 根据id查询自媒体文章
     *
     * @param id
     * @return
     */
    @Override
    public ResponseResult findWmNewsById(Integer id) {
        //1 参数检查
        if (id == null) {
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID);
        }
        WmNews wmNews = this.getById(id);
        if (wmNews == null) {
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        //3 返回结果
        ResponseResult responseResult = ResponseResult.okResult(wmNews);
        responseResult.setHost(webSite);
        return responseResult;
    }

    /**
     * 自媒体文章的删除
     *
     * @param id
     * @return
     */
    @Override
    public ResponseResult deleteNews(Integer id) {
        //1 参数检查
        if (id == null) {
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2.获取数据
        WmNews wmNews = getById(id);
        if (wmNews == null) {
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID);
        }
        //3.判断当前文章的状态  status==9  enable == 1
        if (wmNews.getStatus().equals(WmNews.Status.PUBLISHED.getCode()) && wmNews.getEnable().equals(WemediaConstants.WM_NEWS_ENABLE_UP)) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "文章已发布，不能删除");
        }
        //先删除文章与素材之间的关系
        wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery()
                .eq(WmNewsMaterial::getNewsId, wmNews.getId()));
        //然后再删除文章
        removeById(wmNews.getId());

        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);

    }

    /**
     * 自媒体文章的上架或者下架
     * - 当前已经发布（状态为9）的文章可以上架（enable = 1），也可以下架（enable = 0）
     * TODO 在上架和下架操作的同时，需要同步app端的文章配置信息，暂时不做，后期讲到审核文章的时候再优化
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult downOrUp(WmNewsDto dto) {
        //1.检查参数
        if (dto == null || dto.getId() == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        if (!WemediaConstants.WM_NEWS_ENABLE_UP.equals(dto.getEnable())
                && !WemediaConstants.WM_NEWS_ENABLE_DOWN.equals(dto.getEnable())) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "上下架状态错误");
        }
        WmNews wmNews = getById(dto.getId());
        if (!wmNews.getStatus().equals(WmNews.Status.PUBLISHED.getCode())) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "当前文章不是发布状态，不能上下架");
        }

        LambdaUpdateWrapper<WmNews> updateWeapper = Wrappers.<WmNews>lambdaUpdate();
        updateWeapper.set(WmNews::getEnable, dto.getEnable());
        if (dto.getEnable().equals(WemediaConstants.WM_NEWS_ENABLE_DOWN)) {
            updateWeapper.set(WmNews::getStatus, WemediaConstants.WM_NEWS_SUMMIT_STATUS);
        }
        updateWeapper.eq(WmNews::getId, dto.getId())
                .eq(WmNews::getUserId, WmThreadLocalUtils.getUser().getId());
        update(updateWeapper);
        if (wmNews.getArticleId() != null) {
              Map<String,Object> map = new HashMap<>();
              map.put("enable",dto.getEnable());
              map.put("articleId",wmNews.getArticleId());
              kafkaTemplate.send(WmNewsMessageConstants.WM_NEWS_UP_OR_DOWN_TOPIC,JSON.toJSONString(map));
             log.info("文章上下架 同步通知article ");
        }
        //是否上架  0 下架  1 上架
        // ==============新加代码==============================================
        Map map = new HashMap();
        map.put("articleId",wmNews.getArticleId());
        if(dto.getEnable().intValue() == 0){
            kafkaTemplate.send(ArticleForEsConstants.ARTICLE_ELASTICSEARCH_DELETE,JSON.toJSONString(map));
            log.info("kafka下架通知发送消息+{}",map);
        }else {
            kafkaTemplate.send(ArticleForEsConstants.ARTICLE_ELASTICSEARCH_ADD,JSON.toJSONString(map));
            log.info("kafka上架通知发送消息+{}",wmNews.getArticleId());
        }
        // ==============新加代码==============================================
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);

    }

    /**
     * 查询需要发布的文章id列表
     * 查询状态为4或为8的数据且发布时间小于当前时间
     *
     * @return
     */
    @Override
    public List<Integer> findRelase() {
        List<WmNews> wmNewsList = this.list(Wrappers.<WmNews>lambdaQuery()
                .le(WmNews::getPublishTime, new Date())
                .in(WmNews::getStatus, 4, 8)
                .select(WmNews::getId));
        //只保留id
        List<Integer> idList = wmNewsList.stream().map(WmNews::getId).collect(Collectors.toList());
        return idList;
    }

    @Autowired
    WmNewsMapper wmNewsMapper;

    /**
     * 人工审核文章(可根据title模糊查询,可选择状态,不选择就是全部) 分页查询 统计count 封装返回
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult findList(NewsAuthDto dto) {
        if (dto == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        dto.checkParam();
        int currentPage = dto.getPage();
        dto.setPage((dto.getPage() - 1) * dto.getSize());
        if (dto.getTitle() != null) {
            dto.setTitle("%" + dto.getTitle() + "%");
        }
        List<WmNewsVo> wmNewsVoList = wmNewsMapper.findListAndPage(dto);
        long count = wmNewsMapper.findListCount(dto);

        //封装参数
        PageResponseResult pageResponseResult = new PageResponseResult(currentPage, dto.getSize(), count);
        pageResponseResult.setData(wmNewsVoList);
        pageResponseResult.setHost(webSite);

        return pageResponseResult;

    }

    @Autowired
    private WmUserMapper wmUserMapper;

    /**
     * 根据文章id查询文章详情(Vo中内容加文章作者)
     *
     * @param id
     * @return
     */
    @Override
    public ResponseResult findWmNewsVo(Integer id) {
        //参数检查
        if (id == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        WmNews wmNews = this.getById(id);
        if (wmNews == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        WmUser wmUser = this.wmUserMapper.selectById(wmNews.getUserId());
        if (wmUser == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        WmNewsVo wmNewsVo = BeanUtil.toBeanIgnoreError(wmNews, WmNewsVo.class);
        wmNewsVo.setAuthorName(wmUser.getName());

        return ResponseResult.okResult(wmNewsVo);
    }

    /**
     * 修改文章(人工审核 通过 status=4 失败 status = 2 )
     *
     * @param wmNewsAuthPass
     * @param dto
     * @return
     */
    @Override
    public ResponseResult updateStatus(Short status, NewsAuthDto dto) {
        //1.参数检查
        if (dto == null || dto.getId() == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        WmNews wmNews = this.getById(dto.getId());
        wmNews.setStatus(status);

        if (wmNews == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        if (dto.getMsg() != null) {
            wmNews.setReason(dto.getMsg());
        }
        updateById(wmNews);

        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    /**
     * 保存封面和引用素材的关联关系
     * 如果封面是自动生成 需要按照规则 从内容图片中获取封面
     *
     * @param contentImages 内容中图片列表
     * @param wmNews        要保存的文章信息
     * @param dto           入参实体类
     */
    private void saveRelativeInfoForCover(List<String> contentImages, WmNews wmNews, WmNewsDto dto) {
        List<String> imageList = dto.getImages();
        if (WemediaConstants.WM_NEWS_TYPE_AUTO.equals(dto.getType())) {
            if (CollUtil.isNotEmpty(contentImages)) {
                int size = contentImages.size();
                //如果图片数量大于2 type=3 多图 取内容中的前三张作为封面
                if (size > 2) {
                    imageList = contentImages.stream().limit(3).collect(Collectors.toList());
                    wmNews.setType(WemediaConstants.WM_NEWS_MANY_IMAGE);
                } else if (size > 0 && size <= 2) {
                    imageList = contentImages.stream().limit(1).collect(Collectors.toList());
                    wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE);
                }
                //如果图片数量大于0 小于2 type=1 单图 取内容前一张作为封面
            } else {
                wmNews.setType((short) 0);//无图模式
            }
            //图片封面图片转为逗号拼接的字符串 去除前缀
            wmNews.setImages(imageListToStr(imageList));
            this.updateById(wmNews);

            if (CollUtil.isNotEmpty(imageList)) {
                //替换路径前缀
                imageList = imageList.stream().map(url -> url.replaceAll(webSite, "")).collect(Collectors.toList());
                //保存 封面与素材的关联关系
                saveRelativeInfo(imageList, wmNews.getId(), WemediaConstants.WM_IMAGE_REFERENCE);
            }
        }

    }

    /**
     * 保存 内容中引用素材的关联关系
     *
     * @param contentImages
     * @param wmNews
     */
    private void saveRelativeInfoForContent(List<String> contentImages, WmNews wmNews) {
        saveRelativeInfo(contentImages, wmNews.getId(), WemediaConstants.WM_CONTENT_REFERENCE);
    }

    /**
     * 保存素材和文章关系
     *
     * @param materialUrls 素材列表
     * @param newsId       文章ID
     * @param type         类型 0：内容素材  1：封面素材
     */
    public void saveRelativeInfo(List<String> materialUrls, Integer newsId, Short type) {
        //根据素材路径集合 及登录userId 查询素材集合
        List<WmMaterial> wmMaterials = materialMapper.selectList(Wrappers.<WmMaterial>lambdaQuery()
                .eq(WmMaterial::getUserId, WmThreadLocalUtils.getUser().getId())
                .in(WmMaterial::getUrl, materialUrls));
        //将素材列表转为map集合 key 图片url路径  value 素材的id
        Map<String, Integer> urlAndIdMap = wmMaterials.stream()
                .collect(Collectors.toMap(WmMaterial::getUrl, WmMaterial::getId));
        //查看引用素材路径列表,在map中是否引用,如果有没有引用的 抛出异常
        List<Integer> materialIds = new ArrayList<>();
        materialUrls.forEach(url -> {
            if (!urlAndIdMap.containsKey(url)) {
                throw new CustomException(AppHttpCodeEnum.PARAM_INVALID, "引用的素材不存在");
            }
            materialIds.add(urlAndIdMap.get(url));
        });

        wmNewsMaterialMapper.saveRelations(materialIds, newsId, type);
    }

    /**
     * 将文章内容中的图片抽取出来
     *
     * @param content
     * @return
     */
    private List<String> parseContentImages(String content) {
        //使用fastjson 将文章内容转为list集合 集合中的每一个元素 都是一个Map
//        List<Map> contentImages = JSONArray.parseArray(content,Map.class);
//        List<String> contextImageList = contentImages.stream()
//                .filter(map -> WemediaConstants.WM_NEWS_TYPE_IMAGE.equals(map.get("type")))//过滤保留type等于images的数据
//                .map(map -> (String) map.get("value"))//映射 获取每个map中 value的值
//                .map(url -> url.replaceAll(webSite, "")) //替换掉路径的访问前缀
//                .collect(Collectors.toList()); //收集到集合中

        List<Map> contentImages = JSONArray.parseArray(content, Map.class);
        List<String> contentImageList = contentImages.stream()   // 定义流
                .filter(map -> WemediaConstants.WM_NEWS_TYPE_IMAGE.equals(map.get("type"))) // 过滤 保留 type=image
                .map(map -> (String) map.get("value"))  // 映射 获取每个map中 value的值
                .map(url -> url.replaceAll(webSite, "")) // 替换掉路径的访问前缀
                .collect(Collectors.toList()); // 收集到集合中

        return contentImageList;
    }


    /**
     * 保存或修改 WmNews对象
     *
     * @param wmNews
     */
    private void saveOrUpdateWmNews(WmNews wmNews) {
        //1.补全创建时间 上下架
        wmNews.setCreatedTime(new Date());
        wmNews.setSubmitedTime(new Date());
        wmNews.setEnable(WemediaConstants.WM_NEWS_ENABLE_UP);
        //2.判断id的值是否为空  如果为空 新增该wmNews
        if (wmNews.getId() == null) {
            this.save(wmNews);
        } else {
            //3.如果id不为空,先删除之前的关联关系 然后修改wmNews
            wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId, wmNews.getId()));
            this.updateById(wmNews);//修改文章
        }


    }

    /**
     * 将图片列表集合 用逗号拼接成一个字符串 并且替换前缀路径
     *
     * @param imageList
     * @return
     */
    private String imageListToStr(List<String> imageList) {
        return imageList.stream()
                .map(url -> url.replaceAll(webSite, "")) //将流中的url路径替换前缀
                .collect(Collectors.joining(","));//将所有路径 用逗号拼接成字符串

    }
}
